package com.geqian.web.aop;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.geqian.web.log.ApiLogHandler;
import com.geqian.web.pojo.DetailedLog;
import com.geqian.web.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

@Aspect
@Slf4j
@Component
public class LogAspect {

    private final String POINT_CUT = "execution(* com.geqian.web.controller.*.*(..)) " +
            "&& !@within(com.geqian.web.annotation.ExcludeLog) " +
            "&& !@annotation(com.geqian.web.annotation.ExcludeLog))";


    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Resource
    private ObjectMapper objectMapper;

    @Pointcut(POINT_CUT)
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {

        long startTime = System.currentTimeMillis();
        Object result = point.proceed(point.getArgs());
        long endTime = System.currentTimeMillis();

        try {
            //获得请求信息
            HttpServletRequest request = getRequest();
            //获取响应信息
            HttpServletResponse response = getResponse();
            //获取详细日志信息
            DetailedLog detailedLog = packagingLogs(request, response, point, result, endTime - startTime);
            //将信息日志信息保存到日志队列中
            ApiLogHandler.pushLog(detailedLog);

        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 打包日志信息
     *
     * @param request
     * @param response
     * @param point
     * @return
     * @throws Throwable
     */
    private DetailedLog packagingLogs(HttpServletRequest request, HttpServletResponse response, ProceedingJoinPoint point, Object result, long speedTime) {
        try {
            DetailedLog detailedLog = new DetailedLog();
            if (request != null) {
                String ip = "0:0:0:0:0:0:0:1".equals(request.getRemoteAddr()) ? "127.0.0.1" : request.getRemoteAddr();
                String requestURL = request.getRequestURL().toString();
                Map<String, Object> parameters = getParameters(point);
                Map<String, Object> headers = getHeaders(point);
                String method = request.getMethod();
                String apiMethod = point.getSignature().getDeclaringTypeName() + "." + point.getSignature().getName();
                String token = request.getHeader("token");
                if (token != null) {
                    try {
                        String userName = JwtUtils.parseForValue(token, "userName", String.class);
                        detailedLog.setUserName(userName);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                detailedLog.setApiMethod(apiMethod);
                detailedLog.setIp(ip);
                detailedLog.setRequestMethod(method);
                detailedLog.setHeaders(objectMapper.writeValueAsString(headers));
                detailedLog.setUrl(requestURL);
                detailedLog.setParameters(objectMapper.writeValueAsString(parameters));
            }
            if (response != null) {
                int status = response.getStatus();
                detailedLog.setResponseStatus(status);
                detailedLog.setResponseBody(objectMapper.writeValueAsString(result));
            }
            String accessTime = formatter.format(LocalDateTime.now());
            detailedLog.setSpendTime(formatSpeedTime(speedTime));
            detailedLog.setAccessTime(accessTime);
            return detailedLog;
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }



    /**
     * 格式化执行时间
     *
     * @param speedTime 执行时间
     * @return
     */
    private String formatSpeedTime(long speedTime) {
        if (speedTime >= 1000) {
            return (double) speedTime / 1000 + "s";
        }
        return speedTime + "ms";
    }

    /**
     * 获取请求参数
     *
     * @param point 切入点
     * @return 请求参数
     */
    private Map<String, Object> getParameters(JoinPoint point) {
        ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取参数名称数组
        String[] argsName = parameterNameDiscoverer.getParameterNames(method);
        //参数值
        Object[] args = point.getArgs();
        //方法参数对象
        Parameter[] parameters = method.getParameters();
        Map<String, Object> params = new HashMap<>();
        if (argsName != null && argsName.length > 0) {
            for (int i = 0; i < argsName.length; i++) {
                if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || isRequestHeader(parameters[i])) {
                    break;
                }
                params.put(argsName[i], args[i]);
            }
        }
        return params;
    }

    /**
     * 获取请求头参数
     *
     * @param point 切入点
     * @return 请求参数
     */
    private Map<String, Object> getHeaders(JoinPoint point) {
        ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        //获取参数名称数组
        String[] argsName = parameterNameDiscoverer.getParameterNames(method);
        //参数值
        Object[] args = point.getArgs();
        //方法参数对象
        Parameter[] parameters = method.getParameters();

        Map<String, Object> headers = new HashMap<>();
        if (argsName != null && argsName.length > 0) {
            for (int i = 0; i < argsName.length; i++) {
                if (isRequestHeader(parameters[i])) {
                    headers.put(argsName[i], args[i]);
                }
            }
        }
        return headers;
    }

    /**
     * 获取请求
     *
     * @return
     */
    private HttpServletRequest getRequest() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return servletRequestAttributes != null ? servletRequestAttributes.getRequest() : null;
    }

    /**
     * 获取响应
     *
     * @return
     */
    private HttpServletResponse getResponse() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return servletRequestAttributes != null ? servletRequestAttributes.getResponse() : null;
    }


    /**
     * 判断参数是否为请求头参数
     *
     * @param parameter 参数对象
     * @return
     */
    public static boolean isRequestHeader(Parameter parameter) {
        return parameter.isAnnotationPresent(RequestHeader.class);
    }
}